package top.gytf.unifiedcall.jsr269.code;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;
import top.gytf.unifiedcall.jsr269.AbstractUnifiedCallProcessor;
import top.gytf.unifiedcall.jsr269.Utils;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.util.List;
import java.util.Set;

import static java.lang.String.format;

/**
 * 生成CodeLocation常量
 */
public class CodeLocationGenerateProcessor extends AbstractUnifiedCallProcessor {
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return CollUtil.newHashSet(CodeLocationConstants.class.getName());
    }

    @Override
    public int order() {
        return Integer.MIN_VALUE;
    }

    @Override
    public void process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(CodeLocationConstants.class);

        for (Element element : elements) {
            if (element.getKind() != ElementKind.ENUM) {
                messager.printMessage(Diagnostic.Kind.ERROR, "CodeLocationConstants仅允许注解在enum上", element);
                continue;
            }
            TypeElement enumElement = (TypeElement) element;
            List<VariableElement> candidates = findCandidates(element);
            CodeLocationConstants codeLocationConstants = element.getAnnotation(CodeLocationConstants.class);

            // 获取被注解类所在包名
            ClassName enumClassName = ClassName.get(enumElement);
            // 拼接生成Locations常量类的简单类名
            String simpleClassName = enumElement.getSimpleName() + codeLocationConstants.value();
            ClassName locationConstantClassName = ClassName.get(enumClassName.packageName(), simpleClassName);

            // 检查是否存在
            TypeElement existed = elementUtils.getTypeElement(locationConstantClassName.reflectionName());
            if (existed != null) {
                messager.printMessage(Diagnostic.Kind.WARNING, format("跳过生成[%s]：名称已被占用", enumClassName.reflectionName()));
                return;
            }

            // 生成类
            TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(simpleClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addJavadoc("Auto Generate by UnifiedCall.");
            // 生成常量字段
            for (VariableElement candidate : candidates) {
                String fieldName = candidate.getSimpleName().toString();
                String fieldValue = format("%s#%s", enumElement.getQualifiedName(), fieldName);
                String fieldComment = format("{@link %s}", fieldValue);

                FieldSpec fieldSpec = FieldSpec.builder(String.class, fieldName, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
                        .initializer("\"" + fieldValue + "\"")
                        .addJavadoc(fieldComment)
                        .build();
                typeSpecBuilder.addField(fieldSpec);
            }
            JavaFile javaFile = JavaFile.builder(enumClassName.packageName(), typeSpecBuilder.build())
                    .build();

            try {
                // 保存
                javaFile.writeTo(filer);
            } catch (IOException e) {
                messager.printMessage(Diagnostic.Kind.ERROR, e.getLocalizedMessage());
            }
        }
    }

    /**
     * 检查给定Element是否需要被处理
     *
     * @return 符合要求的 Code 枚举值 Element列表
     */
    private List<VariableElement> findCandidates(Element enumElement) {
        return Utils.scanCodes(enumElement, messager,
                // 标注这个注解的不需要生成异常
                e -> e.getAnnotation(CodeLocationConstants.Ignore.class) == null);
    }

    /**
     * 获取给定编码常量的定位描述
     *
     * @return 当无生成常量时返回 {@code null}
     */
    public static Pair<ClassName, String> getCodeLocation(VariableElement enumConstElement) {
        TypeElement enumElement = (TypeElement) enumConstElement.getEnclosingElement();
        CodeLocationConstants codeLocationConstants = enumElement.getAnnotation(CodeLocationConstants.class);
        if (codeLocationConstants == null || enumConstElement.getAnnotation(CodeLocationConstants.Ignore.class) != null
                || enumConstElement.getKind() != ElementKind.ENUM_CONSTANT) {
            return null;
        }

        // 获取被注解类所在包名
        ClassName enumClassName = ClassName.get(enumElement);
        return new Pair<>(ClassName.bestGuess(enumClassName.reflectionName() + codeLocationConstants.value()),
                enumConstElement.getSimpleName().toString());
    }
}
